package cn.com.obs;

import cn.com.obs.entity.ClusterSubAccountRespDto;
import cn.com.obs.entity.ObsEntityPageReq;
import cn.com.obs.entity.ObsEntityRep;
import cn.com.obs.entity.ObsFileDto;
import cn.com.obs.entity.ObsResourceTypeEnum;
import com.obs.services.model.HttpMethodEnum;
import lombok.extern.slf4j.Slf4j;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
import software.amazon.awssdk.services.s3.model.Delete;
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
import software.amazon.awssdk.services.s3.model.DeleteObjectsRequest;
import software.amazon.awssdk.services.s3.model.DeleteObjectsResponse;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
import software.amazon.awssdk.services.s3.model.ListObjectsRequest;
import software.amazon.awssdk.services.s3.model.ListObjectsResponse;
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
import software.amazon.awssdk.services.s3.model.PutBucketPolicyRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.model.S3Object;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;
import software.amazon.awssdk.utils.AttributeMap;

import java.net.URI;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

@Slf4j
public class S3Utils {

    public static S3Client getS3Client(ClusterSubAccountRespDto clusterSubAccount) {
        return S3Client.builder()
                       .credentialsProvider(StaticCredentialsProvider
                                                    .create(AwsBasicCredentials.create(clusterSubAccount.getAccessKey(),
                                                                                       clusterSubAccount.getSecretKey())))
                       .endpointOverride(URI.create(clusterSubAccount.getObsUrl()))
                       .region(Region.US_EAST_1)
                       .serviceConfiguration(S3Configuration.builder()
                                                            .pathStyleAccessEnabled(false)
                                                            .chunkedEncodingEnabled(false)
                                                            .build())
                       .build();
    }


    public static S3Client getS3Client(ClusterSubAccountRespDto clusterSubAccount, String clusterType) {
        Region region = Region.US_EAST_1;

        S3ClientBuilder s3ClientBuilder = S3Client.builder()
                .credentialsProvider(StaticCredentialsProvider.create(
                        AwsBasicCredentials.create(clusterSubAccount.getAccessKey(),
                                clusterSubAccount.getSecretKey())))
                .endpointOverride(URI.create(clusterSubAccount.getObsUrl()))
                .region(region)
                .serviceConfiguration(S3Configuration.builder()
                        .pathStyleAccessEnabled(true)
                        .chunkedEncodingEnabled(false)
                        .build());

        if (!"SLURM".equals(clusterType)) {
            AttributeMap attributeMap = AttributeMap.builder()
                                                    .put(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES, true)
                                                    .build();
            SdkHttpClient httpClient = ApacheHttpClient.builder()
                                                       .buildWithDefaults(attributeMap);
            s3ClientBuilder.httpClient(httpClient);
        }

        return s3ClientBuilder.build();
    }

    /**
     * 创建文件夹
     *
     * @param clusterSubAccount 集群子账户
     * @param objectKey 对象键
     * @param clusterType 集群类型
     *
     * @return boolean
     */
    public static boolean createFolder(ClusterSubAccountRespDto clusterSubAccount, String objectKey, String clusterType) {
        try {
            S3Client s3Client = getS3Client(clusterSubAccount, clusterType);
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                                                                .bucket(clusterSubAccount.getBucketName())
                                                                .key(objectKey + "/")
                                                                .build();
            s3Client.putObject(putObjectRequest, RequestBody.fromBytes(new byte[0]));
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 列出对象
     *
     * @param req 绿色
     * @param clusterSubAccount 集群子账户
     * @param clusterType 集群类型
     *
     * @return {@link ObsEntityRep }
     */
    public static ObsEntityRep listObjects(ObsEntityPageReq req, ClusterSubAccountRespDto clusterSubAccount, String clusterType) {
        S3Client s3Client = getS3Client(clusterSubAccount, clusterType);
        ListObjectsRequest request = ListObjectsRequest.builder()
                                                       .bucket(clusterSubAccount.getBucketName())
                                                       .prefix(req.getPrefix())
                                                       .marker(req.getMarker())
                                                       .delimiter(req.getDelimiter())
                                                       .maxKeys(req.getMaxKeys())
                                                       .build();
        ObsEntityRep obsEntityRep = new ObsEntityRep();
        List<ObsFileDto> obsFileDtos = new LinkedList<>();
        ListObjectsResponse obsResult = s3Client.listObjects(request);
        obsResult.commonPrefixes()
                 .forEach(prefixes -> {
                     ObsFileDto obsFileDTO = new ObsFileDto();
                     String[] preFixesArr = prefixes.prefix().split("/");
                     obsFileDTO.setFileName(preFixesArr[preFixesArr.length - 1]);
                     obsFileDTO.setResourceType(ObsResourceTypeEnum.FOLDER.getType());
                     obsFileDtos.add(obsFileDTO);
                 });
        obsResult.contents()
                 .forEach(result -> {
                     ObsFileDto obsFileDTO = new ObsFileDto();
                     String[] preFixesArr = result.key().split("/");
                     obsFileDTO.setFileName(preFixesArr[preFixesArr.length - 1]);
                     obsFileDTO.setResourceType(ObsResourceTypeEnum.FILE.getType());
                     obsFileDTO.setFileSize(result.size());
                     obsFileDTO.setModifyDate(Date.from(result.lastModified()));
                     obsFileDtos.add(obsFileDTO);
                 });
        obsEntityRep.setFileList(obsFileDtos);
        obsEntityRep.setNextMarker(obsResult.nextMarker());
        return obsEntityRep;
    }

    /**
     * 获取obs临时凭证
     *
     * @param clusterSubAccount 集群子账户
     * @param objectKey 对象键
     * @param req 绿色
     *
     * @return {@link String }
     */
    public static String getObsTemporaryVoucher(ClusterSubAccountRespDto clusterSubAccount, String objectKey, ObsEntityPageReq req, String method) {
        S3Presigner s3Presigner = S3Presigner.builder()
                .credentialsProvider(StaticCredentialsProvider
                        .create(AwsBasicCredentials.create(clusterSubAccount.getAccessKey(),
                                clusterSubAccount.getSecretKey())))
                .endpointOverride(URI.create(clusterSubAccount.getObsUrl()))
                .serviceConfiguration(S3Configuration.builder()
                        .pathStyleAccessEnabled(true)
                        .chunkedEncodingEnabled(false)
                        .build())
                .region(Region.US_EAST_1)
                .build();
        if (HttpMethodEnum.PUT.getOperationType().equals(method)) {
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                                                                .bucket(clusterSubAccount.getBucketName())
                                                                .key(objectKey)
                                                                .build();
            PutObjectPresignRequest putObjectPresignRequest = PutObjectPresignRequest.builder()
                                                                                     .putObjectRequest(putObjectRequest)
                                                                                     .signatureDuration(
                                                                                             Duration.ofSeconds(10000L))
                                                                                     .build();
            PresignedPutObjectRequest presignedPutObjectRequest = s3Presigner.presignPutObject(putObjectPresignRequest);
            return presignedPutObjectRequest.url().toString();
        } else if (HttpMethodEnum.GET.getOperationType().equals(method)) {
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                                                                .bucket(clusterSubAccount.getBucketName())
                                                                .key(objectKey)
                                                                .build();
            GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
                                                                                     .getObjectRequest(getObjectRequest)
                                                                                     .signatureDuration(
                                                                                             Duration.ofSeconds(10000L))
                                                                                     .build();
            PresignedGetObjectRequest presignedGetObjectRequest = s3Presigner.presignGetObject(getObjectPresignRequest);
            return presignedGetObjectRequest.url().toString();
        }
        return null;
    }

    /**
     * 删除对象
     *
     * @param clusterSubAccount 集群子账户
     * @param objectKeys 对象关键帧
     * @param clusterType 集群类型
     *
     * @return boolean
     */
    public static boolean deleteObject(ClusterSubAccountRespDto clusterSubAccount, List<String> objectKeys, String clusterType) {
        try {
            S3Client s3Client = getS3Client(clusterSubAccount, clusterType);
            ListObjectsRequest request = ListObjectsRequest.builder()
                                                           .bucket(clusterSubAccount.getBucketName())
                                                           .maxKeys(1000)
                                                           .build();
            recursionDeleteObs(s3Client, request, clusterSubAccount.getBucketName(), objectKeys);
        } catch (Exception e) {
            log.error("文件删除失败,reason[{}], [userId:{},objectKey:{}]", e.getMessage(), clusterSubAccount.getAccessKey(), objectKeys);
        }
        return true;
    }

    /**
     * 递归删除obs
     *
     * @param s3Client s3客户端
     * @param request 要求
     * @param bucketName bucket名称
     * @param deleteObjectKeys 删除对象关键点
     */
    private static void recursionDeleteObs(S3Client s3Client, ListObjectsRequest request, String bucketName, List<String> deleteObjectKeys) {
        while (true) {
            List<String> folderKeys = deleteObs(s3Client, request, bucketName, deleteObjectKeys);
            if (folderKeys.isEmpty()) {
                return;
            }
            recursionDeleteObs(s3Client, request, bucketName, folderKeys);
        }
    }

    /**
     * 删除obs
     *
     * @param s3Client s3客户端
     * @param request 要求
     * @param bucketName bucket名称
     * @param deleteObjectKeys 删除对象关键点
     *
     * @return {@link List }<{@link String }>
     */
    private static List<String> deleteObs(S3Client s3Client, ListObjectsRequest request, String bucketName, List<String> deleteObjectKeys) {
        List<String> deleteFileKeys = new ArrayList<>();
        List<String> folderKeys = new ArrayList<>();
        ListObjectsResponse resultListen;
        for (String key : deleteObjectKeys) {
            request = request.toBuilder().prefix(key).build();
            do {
                resultListen = s3Client.listObjects(request);
                if (resultListen.contents().size() == 1) {
                    doDeleteObs(s3Client, bucketName, Collections.singletonList(key));
                    continue;
                }
                for (S3Object result : resultListen.contents()) {
                    if (result.size() != null && result.size() != 0) {
                        deleteFileKeys.add(result.key());
                    } else {
                        folderKeys.add(result.key());
                    }
                }
            } while (resultListen.isTruncated());
        }
        doDeleteObs(s3Client, bucketName, deleteFileKeys);
        return folderKeys;
    }

    /**
     * 删除obs
     *
     * @param s3Client s3客户端
     * @param bucketName bucket名称
     * @param objectKeys 对象关键帧
     */
    private static void doDeleteObs(S3Client s3Client, String bucketName, List<String> objectKeys) {
        if (objectKeys.isEmpty()) {
            return;
        }
        DeleteObjectsRequest.Builder deleteRequestBuilder = DeleteObjectsRequest.builder().bucket(bucketName);
        Delete.Builder deletes = Delete.builder().quiet(true);
        List<ObjectIdentifier> objectIdentifiers = new ArrayList<>();
        for (String objectKey : objectKeys) {
            ObjectIdentifier objectIdentifier = ObjectIdentifier.builder().key(objectKey).build();
            objectIdentifiers.add(objectIdentifier);
        }
        deletes.objects(objectIdentifiers);
        deleteRequestBuilder.delete(deletes.build());
        DeleteObjectsResponse deleteResult = s3Client.deleteObjects(deleteRequestBuilder.build());
        if (!deleteResult.errors().isEmpty()) {
            log.error("文件删除失败[{}]", deleteResult.errors());
        }
    }

    /**
     * 创建bucket
     *
     * @param clusterSubAccount 集群子账户
     * @param clusterType 集群类型
     * @param bucketName bucket名称
     */
    public static void createBucket(ClusterSubAccountRespDto clusterSubAccount, String clusterType, String bucketName) {
        S3Client s3Client = getS3Client(clusterSubAccount, clusterType);
        try {
            if (itExistBucket(bucketName, s3Client)) {
                throw new RuntimeException("bucket已存在");
            }
            s3Client.createBucket(CreateBucketRequest.builder()

                                                     .bucket(bucketName)
                                                     .build());
        } catch (Exception e) {
            log.error("创建bucket失败,reason[{}]", e.getMessage());
            throw new RuntimeException("创建bucket失败");
        }
    }

    /**
     * 删除bucket
     *
     * @param clusterSubAccount 集群子账户
     * @param clusterType 集群类型
     * @param bucketName bucket名称
     */
    public static void deleteBucket(ClusterSubAccountRespDto clusterSubAccount, String clusterType, String bucketName) {
        S3Client s3Client = getS3Client(clusterSubAccount, clusterType);
        try {
            if (!itExistBucket(bucketName, s3Client)) {
                return;
            }
            s3Client.deleteBucket(DeleteBucketRequest.builder()
                                                     .bucket(bucketName)
                                                     .build());
        } catch (Exception e) {
            log.error("删除bucket失败,reason[{}]", e.getMessage());
            throw new RuntimeException("删除bucket失败");
        }
    }

    /**
     * 判断bucket是否存在
     *
     * @param bucketName bucket名称
     * @param s3Client s3客户端
     *
     * @return boolean
     */
    private static boolean itExistBucket(String bucketName, S3Client s3Client) {
        boolean exist = true;
        try {
            HeadBucketRequest headBucketRequest = HeadBucketRequest.builder()
                                                                   .bucket(bucketName)
                                                                   .build();
            s3Client.headBucket(headBucketRequest);
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }

    public static void setBucketPolicy(ClusterSubAccountRespDto clusterSubAccount, String bucketName, String policyDocument) {
        S3Client s3Client = getS3Client(clusterSubAccount, null);
        try {
            PutBucketPolicyRequest putBucketPolicyRequest = PutBucketPolicyRequest.builder()
                                                                                  .bucket(bucketName)
                                                                                  .policy(policyDocument)
                                                                                  .build();

            s3Client.putBucketPolicy(putBucketPolicyRequest);
        } catch (S3Exception e) {
            log.error("设置bucket策略失败,reason[{}]", e.getMessage());
            throw new RuntimeException("设置bucket策略失败");
        }
    }
}
